/*       Filename:  zipmap.c
 *         Author:  MIEN
 *    Description:  在一个缓冲区中，存储少量的字符串到字符串的映射(string->string)，可添加、查询和遍历。
 *                  可直接声明定长的缓冲区使用，不必频繁地申请和释放内存。
 *           Date:  2015-07-07
 */

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zipmap.h"

/* 
 * [存储格式]
 *
 * 前七个字节:
 * | -------------------------------------------------
 * | byte0 | byte1 byte2 | byte3 byte4 | byte5 byte6 |
 * | -------------------------------------------------
 * |  num  |    size     |   offset    |    iter     |
 * | -------------------------------------------------
 * byte0 记录kv的数目
 * byte1,byte2 记录缓冲区大小，以防溢出
 * byte3,byte4 记录下一次添加kv时的偏移量
 * byte5,byte6 记录迭代时当前kv对所在的偏移量
 *
 * 从第8个字节开始，记录kv键值对，格式是:
 * klen,key,'\0',vlen,val,'\0'，如此重复...
 *
 * [注意]
 * 最多只允许存储63个键值对
 * 键和值的长度分别不超过254
 * 缓存区长度不能小于16，最大需要32k, 缓冲区满后，会添加kv失败
 * 多次添加相同key值，取值时返回最早添加的
 */

/* 初始zipmap，清空所有kv对 */
int zipInit(char *buf, int size)
{
    assert(size > 15);

    buf[0] = 0;
    buf[1] = (size >> 8) & 0x7f;
    buf[2] = size & 0xff;
    buf[3] = 0;
    buf[4] = 7;
    buf[5] = 0;
    buf[6] = 7;

    return 0;
}

/* kv对数量 */
int zipNum(char *buf)
{
    return (unsigned char) buf[0];
}

/* 根据key，获取val */
char *zipGet(char *buf, char *key)
{
    int num = (unsigned char) buf[0];
    int l, k;
    int klen = strlen(key);
    char *p = buf + 7;
    while (num > 0) {
        l = *((unsigned char*) p);
        p++;
        if (l == klen && strcmp(p, key) == 0) {
            return (p + l + 2);
        }
        p += l + 1;
        l = *((unsigned char*) p);
        p += l + 2;
        num--;
    }
    return NULL;
}

/* 添加一组kv对 */
int zipAdd(char *buf, char *key, char *val)
{
    int size   = ((unsigned char) buf[1] << 8) | ((unsigned char) buf[2]);
    int offset = ((unsigned char) buf[3] << 8) | ((unsigned char) buf[4]);

    /* too many maps */
    if (buf[0] >= 63)
        return 1;
    
    /* buffer is full */
    if (offset >= size)
        return 2;

    int kl = strlen(key);
    int vl = strlen(val);

    /* key or val is too long */
    if (kl > 254 || vl > 254)
        return 3;

    /* not enough space to store key and val */
    if (offset + kl + vl + 4 > size)
        return 4;

    /* copy key */
    *((unsigned char*) (buf + offset)) = kl;
    offset++;
    strncpy(buf + offset, key, kl);
    offset += kl;
    buf[offset++] = '\0';
    
    /* copy val */
    *((unsigned char*) (buf + offset)) = vl;
    offset++;
    strncpy(buf + offset, val, vl);
    offset += vl;
    buf[offset++] = '\0';

    /* set offset */
    buf[3] = (offset >> 8) & 0x7f;
    buf[4] = offset & 0xff;

    /* num + 1 */
    buf[0] += 1;

    /* add ok */
    return 0;
}

/* 重置zipmap，以备遍历(并非清空kv对，而是iter指针指向第一对kv) */
void zipReset(char *buf)
{
    buf[5] = 0;
    buf[6] = 7;
}

/* 遍历, 取出当前kv对，并把iter指针指向下一组 */
int zipNext(char *buf, char **key, char **val)
{
    *key = NULL;
    *val = NULL;

    int offset = ((unsigned char) buf[3] << 8) | ((unsigned char) buf[4]);
    int iter   = ((unsigned char) buf[5] << 8) | ((unsigned char) buf[6]);

    if (iter >= offset)
        return 0;
    
    int kl, vl;

    kl = *((unsigned char*) (buf + iter));
    iter++;
    *key = buf + iter;
    iter += kl + 1;
    
    vl = *((unsigned char*) (buf + iter));
    iter++;
    *val = buf + iter;
    iter += vl + 1;

    buf[5] = (iter >> 8) & 0x7f;
    buf[6] = iter & 0xff;

    return iter;
}
